# @Time : 2020/10/8 20:01 
# @Author : Michael
import math
import cv2 as cv
import numpy as np
from math import pi, cos, sin

# image_pic = "ammeter.jpg"
image_pic = "pic21.png"


class Config:
    def __init__(self):
        pass

    resizeRate = 0.5
    min_area = 5000
    min_contours = 8
    threshold_thresh = 50
    epsilon_start = 10
    epsilon_step = 10


class HSVColor:
    def __init__(self):
        pass

    red_low = np.array([0, 43, 46])
    green_low = np.array([35, 43, 46])
    blue_low = np.array([100, 43, 46])

    red_high = np.array([10, 255, 255])
    green_high = np.array([77, 255, 255])
    blue_high = np.array([124, 255, 255])


def WhateverShow(windowName, image):
    cv.imshow(windowName, image)
    k = cv.waitKey(0)
    while True:
        if k == ord('q') or k == 27:
            break
    cv.destroyAllWindows()


class METER:

    def __init__(self, image):
        """

        :param image:
        """
        if isinstance(image, str):
            self.processed = self.image = cv.imread(image)
        elif isinstance(image, np.ndarray):
            self.image = image
            self.processed = image

        self.canny = None
        self.contours = None
        self.cnt = None
        self.wr = []
        self.pi = []
        self.center_x = None
        self.center_y = None
        self.radius = None

    def colorChange(self, mode):
        """

        :param mode: "gray" or "hsv"
        :return:
        """
        if mode == "gray":
            self.processed = cv.cvtColor(self.processed, cv.COLOR_BGR2GRAY)
        elif mode == "hsv":
            self.processed = cv.cvtColor(self.processed, cv.COLOR_BGR2HSV)
        else:
            print("colorchanged is wrong!\n", exit("dammit"))
        return self.processed

    def Resize(self, dsize, fx, fy, interplotion):
        self.processed = cv.resize(self.processed, dsize=dsize, fx=fx, fy=fy, interpolation=interplotion)
        return

    def intoprocessed(self, image):  # 读取图片并转换成灰度图
        self.processed = cv.cvtColor(image, cv.COLOR_BGR2GRAY)
        return

    def median_filter(self, ksize):
        """
        中值滤波(奇数)
        :param ksize: 奇数
        :return:
        """
        self.processed = cv.medianBlur(self.processed, ksize=ksize)
        return

    def GaussianBlur(self, kenel):  #
        """
        高斯滤波，图像平滑
        :param kenel:奇数
        :return:
        """
        self.processed = cv.GaussianBlur(self.processed, (kenel, kenel), 0)
        return

    def DoSobel(self, param):  #
        """
        图像梯度
        :param param:0：x方向， 1：y方向
        :return:
        """
        if param == 0:
            self.processed = cv.Sobel(self.processed, -1, 1, 0, ksize=1)
        elif param == 1:
            self.processed = cv.Sobel(self.processed, -1, 0, 1, ksize=1)
        return

    def Laplace(self, parm):  #
        """
        Laplace卷积核大小
        :param parm:
        :return:
        """
        kernel = np.array([[0, -1, 0],
                           [-1, parm, -1],
                           [0, -1, 0]], np.float32)
        self.processed = cv.filter2D(self.processed, -1, kernel=kernel)
        return

    def OTSU_th(self):
        """
        otsu二值化
        :return:
        """
        ret, self.processed = cv.threshold(self.processed, 0, 255, cv.THRESH_BINARY + cv.THRESH_OTSU)

        return

    def Show(self, windowName):
        cv.imshow(windowName, self.processed)
        k = cv.waitKey(0)
        while True:
            if k == ord('q') or k == 27:
                break
        cv.destroyAllWindows()

    def Save(self, where):
        """
        保存图像
        :param:图像位置：../out/
        :return:
        """
        w = where + 'test.jpg'
        cv.imwrite(w, self.processed)
        return

    def Canny(self, param1, param2, apertureSize):
        """
        边缘检测
        :param param1:
        :param param2:
        :param apertureSize:
        :return:
        """
        self.canny = cv.Canny(self.processed, param1, param2, apertureSize=apertureSize)
        # self.canny = self.processed
        return

    def Erode_or_Dilate(self, ED, k, iter):
        """
        腐蚀或膨胀
        :param ED: e->腐蚀; d->膨胀
        :param k: 核大小
        :param iter: 几次
        :return:
        """
        if k == 0:
            kernel = None
        else:
            kernel = np.ones((k, k), np.uint8)

        if ED == 'e':
            self.processed = cv.erode(self.processed, kernel=kernel, iterations=iter)
        elif ED == 'd':
            self.processed = cv.dilate(self.processed, kernel=kernel, iterations=iter)
        return

    def detect_rect(self, flag):
        """
        边缘检测
        :param flag:如果是空，默认透视变换原图，如果是c,变换canny；p：变换process
        :return:
        """
        self.contours, _ = cv.findContours(self.canny, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
        self.cnt = self.contours[0]
        warped = None
        for idx, c in enumerate(self.contours):
            approxBox = boundingBox(idx, c)
            if approxBox is None:
                print("\n")
                continue
            # 获取最小矩形包络
            rect = cv.minAreaRect(approxBox)
            box = cv.boxPoints(rect)
            box = np.int0(box)
            box = box.reshape(4, 2)
            box = order_points(box)
            print("boundingBox：\n", box)

            # 待切割区域的原始位置，
            # approxPolygon 点重排序, [top-left, top-right, bottom-right, bottom-left]
            src_rect = order_points(approxBox)
            print("src_rect：\n", src_rect)

            w, h = point_distance(box[0], box[1]), point_distance(box[1], box[2])
            print("w = %d ,h= %d " % (w, h))

            # 生成透视变换矩阵
            dst_rect = np.array([
                [0, 0],
                [w - 1, 0],
                [w - 1, h - 1],
                [0, h - 1]],
                dtype="float32")

            # 透视变换
            M = cv.getPerspectiveTransform(src_rect, dst_rect)

            if flag is None:
                # 得到透视变换后的图像
                warped = cv.warpPerspective(self.image, M, (w, h))
            elif flag == 'c':
                warped = cv.warpPerspective(self.canny, M, (w, h))
            elif flag == 'p':
                warped = cv.warpPerspective(self.processed, M, (w, h))
            else:
                exit('边缘检测参数错误')
            self.wr.append(warped)
            self.pi.append(idx)
        if len(self.wr) == 1:
            return warped
        else:
            return self.wr

    def approximate(self):
        pass

    def return_data(self, flag):
        if flag is None or flag == 'o':
            return self.image
        elif flag == 'p':
            return self.processed
        elif flag == 'c':
            return self.canny
        elif flag == 'wr':
            return self.wr
        else:
            return None

    def color_trace(self, color):
        mask = None
        if color == "red":
            mask = cv.inRange(self.processed, HSVColor.red_low, HSVColor.red_high)
        elif color == "blue":
            mask = cv.inRange(self.processed, HSVColor.blue_low, HSVColor.blue_high)
        elif color == "green":
            mask = cv.inRange(self.processed, HSVColor.green_low, HSVColor.green_high)
        else:
            exit("color_trace is wrong")
        erosion = cv.erode(mask, np.ones((1, 1), np.uint8), iterations=2)
        dilation = cv.dilate(erosion, np.ones((1, 1), np.uint8), iterations=2)
        target = cv.bitwise_and(self.image, self.image, mask=dilation)

        # 将滤波后的图像变成二值图像放在binary中
        ret, binary = cv.threshold(dilation, 127, 255, cv.THRESH_BINARY)
        # 在binary中发现轮廓，轮廓按照面积从小到大排列
        contours, hierarchy = cv.findContours(binary, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
        m = 0
        for i in contours:
            x, y, w, h = cv.boundingRect(i)  # 将轮廓分解为识别对象的左上角坐标和宽、高
            # 在图像上画上矩形（图片、左上角坐标、右下角坐标、颜色、线条宽度）
            if min(self.image.shape[0], self.image.shape[1]) / 10 < min(h, w) \
                    < min(self.image.shape[0], self.image.shape[1]) / 1:
                cv.rectangle(self.image, (x, y), (x + w, y + h), (0, 255,), 3)
                # 给识别对象写上标号
                font = cv.FONT_HERSHEY_SIMPLEX
                cv.putText(self.image, color, (x - 10, y + 10), font, 1, (0, 0, 255), 2)  # 加减10是调整字符位置
        return self.image

    def detect_circle(self):
        h, w = self.processed.shape[:2]
        circles = cv.HoughCircles(self.processed, cv.HOUGH_GRADIENT, 1, minDist=20, circles=np.array([]), param1=100,
                                  param2=50, minRadius=int(h * 0.35), maxRadius=int(h * 0.80))
        # print(circles)
        a, b, c = circles.shape
        x, y, r = avg_circles(circles, b)
        self.center_x, self.center_y, self.radius = x, y, r
        cv.circle(self.image, (x, y), r, (0, 255, 255), 3, cv.LINE_AA)  # 圆
        cv.circle(self.image, (x, y), 2, (0, 255, 0), 3, cv.LINE_AA)  # 圆心
        print("Radius =", self.radius)
        return x, y, r

    # def get_current_value(self, min_angle, max_angle, min_value, max_value, x, y, r):
    #     pass

    def get_pointer_rad(self):
        c_y, c_x = int(self.center_x), int(self.center_y)
        x1 = c_x + c_x * 0.8
        self.processed = cv.cvtColor(self.processed, cv.COLOR_BGR2RGB)
        src = self.processed.copy()
        freq_list = []
        for i in range(361):
            x = (x1 - c_x) * cos(i * pi / 180) + c_x
            y = (x1 - c_x) * sin(i * pi / 180) + c_y
            temp = src.copy()
            cv.line(temp, (c_x, c_y), (int(x), int(y)), (0, 0, 255), thickness=3)
            # t1 = self.processed.copy()
            # t1[temp[:, :, 2] == 255] = 255
            c = self.processed[temp[:, :, 2] == 255]
            points = c[c == 0]
            freq_list.append((len(points), i))
            # print([len(points), i])
            # cv.imshow('d', temp)
            # cv.imshow('d1', c)
            cv.waitKey(1)
        print('当前像素重合最多:', max(freq_list)[0],
              "\n"
              '指针偏转角度:', 360 - max(freq_list)[1],
              "\n360 - theta =", max(freq_list)[1])
        return max(freq_list, key=lambda x: x[0])

    def draw_pointer(self, theta):
        x1, y1 = int(self.center_x + self.radius * cos(theta * pi / 180)), \
                 int(self.center_x + self.radius * sin(theta * pi / 180))
        cv.line(self.image, (self.center_x, self.center_y), (x1, y1), (0, 255, 0), 1, cv.LINE_AA)
        return


def avg_circles(circles, b):
    avg_x = 0
    avg_y = 0
    avg_r = 0
    for i in range(b):
        # optional - average for multiple circles (can happen when a gauge is at a slight angle)
        avg_x = avg_x + circles[0][i][0]
        avg_y = avg_y + circles[0][i][1]
        avg_r = avg_r + circles[0][i][2]
    avg_x = int(avg_x / (b))
    avg_y = int(avg_y / (b))
    avg_r = int(avg_r / (b))
    return avg_x, avg_y, avg_r


def order_points(pts):
    # initialzie a list of coordinates that will be ordered
    # such that the first entry in the list is the top-left,
    # the second entry is the top-right, the third is the
    # bottom-right, and the fourth is the bottom-left
    rect = np.zeros((4, 2), dtype="float32")

    # the top-left point will have the smallest sum, whereas
    # the bottom-right point will have the largest sum
    s = pts.sum(axis=1)
    rect[0] = pts[np.argmin(s)]
    rect[2] = pts[np.argmax(s)]

    # now, compute the difference between the points, the
    # top-right point will have the smallest difference,
    # whereas the bottom-left will have the largest difference
    diff = np.diff(pts, axis=1)
    rect[1] = pts[np.argmin(diff)]
    rect[3] = pts[np.argmax(diff)]

    # return the ordered coordinates
    return rect


# 求两点间的距离
def point_distance(a, b):
    return int(np.sqrt(np.sum(np.square(a - b))))


def boundingBox(idx, c):
    if len(c) < Config.min_contours:
        return None
    epsilon = Config.epsilon_start
    while True:
        approxBox = cv.approxPolyDP(c, epsilon, True)
        # 求出拟合得到的多边形的面积
        theArea = math.fabs(cv.contourArea(approxBox))
        # 输出拟合信息
        print("contour idx: %d ,contour_len: %d ,epsilon: %d ,approx_len: %d ,approx_area: %s" % (
            idx, len(c), epsilon, len(approxBox), theArea))
        if len(approxBox) < 4:
            return None
        if theArea > Config.min_area:
            if len(approxBox) > 4:
                # epsilon 增长一个步长值
                epsilon += Config.epsilon_step
                continue
            else:  # approx的长度为4，表明已经拟合成矩形了
                # 转换成4*2的数组
                approxBox = approxBox.reshape((4, 2))
                return approxBox
        else:
            print("failed to find boundingBox,idx = %d area=%f" % (idx, theArea))
            return None


class HoffLines:
    def __init__(self, pending, image):
        self.pending = pending
        self.image = image

    def HoffLines(self, threshold, minLineLength, maxLineGap):
        """
        霍夫曼直线检测
        :return: 原图，边界
        """
        h, w = self.image.shape[:2]
        m = min(h, w)

        self.image = cv.cvtColor(self.image, cv.COLOR_BGR2RGB)

        # 直线检测
        lines = cv.HoughLinesP(self.pending, 3, np.pi / 180, threshold=threshold, minLineLength=minLineLength
                               , maxLineGap=maxLineGap)
        # print(self.edges)
        self.pending = cv.cvtColor(self.pending, cv.COLOR_BGR2RGB)

        print(np.squeeze(lines))

        for x1, x2, y1, y2 in np.squeeze(lines):
            cv.line(self.pending, (x1, y1), (x2, y2), (0, 255, 0), 2)
            cv.line(self.image, (x1, y1), (x2, y2), (0, 255, 0), 2)
        return lines

    def return_data(self, flag):
        if flag is None or flag == 'o':
            return self.image
        elif flag == 'e':
            return self.pending
        else:
            return None
